package com.hk.mybatis.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.hk.mybatis.entity.BaseEntity;
import com.hk.mybatis.entity.OrderEntity;
import dubhe.token.core.util.SecurityContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * @author miaoj
 */
@Slf4j
public class AuditingEntityHandler implements MetaObjectHandler, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void insertFill(MetaObject metaObject) {
        if (metaObject.getOriginalObject() instanceof BaseEntity<?>) {
            this.strictInsertFill(metaObject, "createdAt", LocalDateTime::now, LocalDateTime.class);
            this.strictInsertFill(metaObject, "updatedAt", LocalDateTime::now, LocalDateTime.class);
            if (!SecurityContextUtil.getCurrentUserInfo().isAnonymous()) {
                this.strictInsertFill(metaObject, "createdBy", () -> SecurityContextUtil.getCurrentUserInfo().getId(), String.class);
                this.strictInsertFill(metaObject, "updatedBy", () -> SecurityContextUtil.getCurrentUserInfo().getId(), String.class);
            }
        }
        if (metaObject.getOriginalObject() instanceof OrderEntity<?>) {
            try {
                TableInfo tableInfo = findTableInfo(metaObject);
                BaseMapper<OrderEntity<?>> baseMapper = (BaseMapper<OrderEntity<?>>) applicationContext.getBean(Class.forName(tableInfo.getCurrentNamespace()));

                QueryWrapper<OrderEntity<?>> wrapper = new QueryWrapper<>();
                wrapper.isNotNull("\"order\"").orderByDesc("\"order\"").last("limit 1");

                OrderEntity<?> entity = baseMapper.selectOne(wrapper);
                if (entity == null || entity.getOrder() == null) {
                    this.strictInsertFill(metaObject, "order", () -> 100L, Long.class);
                } else {
                    this.strictInsertFill(metaObject, "order", () -> getNextOrder(entity.getOrder()), Long.class);
                }
            } catch (ClassNotFoundException e) {
                // ignore
            }
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        if (metaObject.getOriginalObject() instanceof BaseEntity || metaObject.getOriginalObject() instanceof OrderEntity) {
            this.strictUpdateFill(metaObject, "updatedAt", () -> LocalDateTime.now(ZoneId.systemDefault()), LocalDateTime.class);
            if (!SecurityContextUtil.getCurrentUserInfo().isAnonymous()) {
                this.strictInsertFill(metaObject, "updatedBy", () -> SecurityContextUtil.getCurrentUserInfo().getId(), String.class);
            }
        }
    }

    @Override
    public MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) {
        if (fieldName.equals("updatedAt") || fieldName.equals("updatedBy")) {
            metaObject.setValue(fieldName, fieldVal.get());
            return this;
        }
        if (metaObject.getValue(fieldName) == null) {
            Object obj = fieldVal.get();
            if (Objects.nonNull(obj)) {
                metaObject.setValue(fieldName, obj);
            }
        }
        return this;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private static Long getNextOrder(Long order) {
        return Math.round((double) order / 100 + 1) * 100;
    }
}
